//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Text;
using System.Reflection;
using Microsoft.Cci;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Mobile.ObjectBuilder;

namespace Microsoft.Practices.ObjectBuilder.ObGen
{
	[ConfigurationElementType(typeof(PolicyGeneratorFactoryElement<PropertyPolicyGenerator>))]
	public class PropertyPolicyGenerator : DependencyPolicyGenerator
	{
		public override CodeStatementCollection GenerateStatements(Type typeToProcess, CodeTypeDeclaration generatedType, NamingContext namingContext)
		{
			CodeStatementCollection statements = new CodeStatementCollection();
			base.AddAssemblyReference(typeToProcess.Assembly);
			base.AddNamespaceImport(typeof(PropertySetterPolicy).Namespace);
			base.AddNamespaceImport(typeof(StrongTypedPropertySetterInfo).Namespace);

			//IPropertySetterPolicy existingProperties = existingPolicies.Get<IPropertySetterPolicy>(typeToBuild, idToBuild);
			CodeVariableDeclarationStatement existingProperties = new CodeVariableDeclarationStatement(
				typeof(IPropertySetterPolicy).Name, namingContext.GetVariableName("existingProperties"),
				new CodeMethodInvokeExpression(
					new CodeMethodReferenceExpression(
						new CodeArgumentReferenceExpression("existingPolicies"),
						"Get",
						new CodeTypeReference(typeof(IPropertySetterPolicy).Name)
					),
					new CodeArgumentReferenceExpression("typeToBuild"),
					new CodeArgumentReferenceExpression("idToBuild")
				)
			);
			statements.Add(existingProperties);

			//PropertySetterPolicy policy = new PropertySetterPolicy();
			CodeVariableDeclarationStatement existingPolicy = new CodeVariableDeclarationStatement(typeof(PropertySetterPolicy).Name, namingContext.GetVariableName("policy"), 
				new CodeObjectCreateExpression(typeof(PropertySetterPolicy).Name));
			statements.Add(existingPolicy);
			//policy.Properties.Add
			CodeMethodReferenceExpression addProperty = new CodeMethodReferenceExpression(
				new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(existingPolicy.Name), "Properties"), "Add");

			foreach (System.Reflection.PropertyInfo info in typeToProcess.GetProperties())
			{
				Property property = MetadataCache.GetPropertyMetadata(info);
				AttributeNode injectionAttribute = base.GetInjectionAttribute(property.Attributes, false);

				if (injectionAttribute != null)
				{
					CodeConditionStatement ifAddProperty = GetIfPropertyNotExists(existingProperties.Name, info.Name);
					statements.Add(ifAddProperty);

					string attrName = namingContext.GetVariableName("injectionAttribute");
					ifAddProperty.TrueStatements.AddRange(base.GenerateBlockComment(String.Format(
						CultureInfo.CurrentCulture, 
						Properties.Resources.PropertyComment, 
						typeToProcess.Name, 
						info.Name)));
					ifAddProperty.TrueStatements.AddRange(base.BuildAttributeCreationStatements(injectionAttribute, attrName));

					CodeMemberMethod setterMethod = GenerateSetterMethod(typeToProcess, info, namingContext);
					generatedType.Members.Add(setterMethod);

					//policy.Properties.Add("Value", 
					//	new StrongTypedPropertySetterInfo(
					//		typeof(propertyType), 
					//		injectionAttribute.CreateParameter(typeof(propertyType)),
					//		new StrongTypedPropertySetterInfo.SetValueHandler(SetMockObjectValue)));
					ifAddProperty.TrueStatements.Add(
						// policy.Properties.Add(
						new CodeMethodInvokeExpression(
							addProperty, 
							// "PropName"
							new CodePrimitiveExpression(info.Name), 
							// new StrongTypedPropertySetterInfo(
							new CodeObjectCreateExpression(
								typeof(StrongTypedPropertySetterInfo).Name,
								// typeof(propertyType), 
								new CodeTypeOfExpression(info.PropertyType), 
								// injectionAttribute.CreateParameter(typeof(propertyType)),
								new CodeMethodInvokeExpression(
									new CodeVariableReferenceExpression(attrName), 
									"CreateParameter", 
									new CodeTypeOfExpression(info.PropertyType)
								),
								// new StrongTypedPropertySetterInfo.SetValueHandler(SetMockObjectValue)
								new CodeDelegateCreateExpression(
									new SafeCodeTypeReference(typeof(StrongTypedPropertySetterInfo.SetValueHandler)), 
									new CodeTypeReferenceExpression(generatedType.Name), 
									setterMethod.Name
								)
							)
						)
					);
				}
			}

			statements.Add(base.SetPolicyStatement(typeof(IPropertySetterPolicy), new CodeVariableReferenceExpression(existingPolicy.Name)));

			return statements;
		}

		// if (existingProperties == null || existingProperties.Properties.ContainsKey(propertyName) == false)
		private static CodeConditionStatement GetIfPropertyNotExists(string existingPropertiesVariable, string propertyName)
		{
			CodeConditionStatement condition = new CodeConditionStatement(
				new CodeBinaryOperatorExpression(
					/* existingProperties == null */
					new CodeBinaryOperatorExpression(
						new CodeVariableReferenceExpression(existingPropertiesVariable), CodeBinaryOperatorType.ValueEquality, 
						new CodePrimitiveExpression(null)
					), 
					CodeBinaryOperatorType.BooleanOr, 
					/* existingProperties.Properties.ContainsKey(propertyName) == false */
					new CodeBinaryOperatorExpression(
						new CodeMethodInvokeExpression(
							new CodePropertyReferenceExpression(
								new CodeVariableReferenceExpression(existingPropertiesVariable), 
								"Properties"
							), 
							"ContainsKey", 
							new CodePrimitiveExpression(propertyName)
						), 
						CodeBinaryOperatorType.ValueEquality, 
						new CodePrimitiveExpression(false)
					)
				)
			);

			return condition;
		}
		

		private static CodeMemberMethod GenerateSetterMethod(Type typeToProcess, System.Reflection.PropertyInfo info, NamingContext namingContext)
		{
			// We could have used anonymous delegates, but that cannot be expressed in CodeDom and 
			// would not compile with a VB code provider, so we create a method for it.
			// SetMyTypeMyProperty(object target, object value)
			CodeMemberMethod setterMethod = new CodeMemberMethod();
			setterMethod.Name = namingContext.GetMemberName("Set" + typeToProcess.Name + info.Name);
			setterMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;

			setterMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "target"));
			setterMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "value"));

			// ((MockObject)target).Value = (int)value;
			setterMethod.Statements.Add(
				new CodeAssignStatement(
					new CodePropertyReferenceExpression(
						new CodeCastExpression(typeToProcess, new CodeArgumentReferenceExpression("target")),
						info.Name
					),
					new CodeCastExpression(info.PropertyType, new CodeArgumentReferenceExpression("value"))
				)
			);
			return setterMethod;
		}
	}
}
